﻿// -----------------------------------------------------------------------
// <copyright file="DbmsInfo.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;
    using System.Data;

    /// <summary>
    ///   The database and installed metadata properties.
    /// </summary>
    /// <remarks>
    ///   This class is used to describe what database is used and what metadata is installed
    ///   when using the corresponding domain.
    /// </remarks>
    /// <seealso cref = "Centido.Core.Domain" />
    public sealed class DbmsInfo : IRefreshable
    {
        /// <summary>
        ///   The current domain.
        /// </summary>
        private readonly Domain _domain;

        /// <summary>
        ///   The dbms product name.
        /// </summary>
        private string _productName;

        /// <summary>
        ///   The dbms product version.
        /// </summary>
        private string _productVersion;

        /// <summary>
        ///   The column renaming feature availability.
        /// </summary>
        private bool? _columnRenaming;


        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.DbmsInfo" /> class.
        /// </summary>
        /// <param name = "domain">The current domain.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "domain" /> is null.</exception>
        internal DbmsInfo(Domain domain)
        {
            if (domain == null)
                throw new ArgumentNullException("domain");

            _domain = domain;
        }

        /// <summary>
        ///   Gets the database management system name.
        /// </summary>
        /// <value>The name of the database management system.</value>
        public string ProductName
        {
            get
            {
                if (_productName == null)
                {
                    var productName = _domain.DbAccess.CreateParameter("result", OdbcType.NVarChar, string.Empty);
                    productName.Size = 64;
                    productName.Direction = ParameterDirection.Output;

                    _domain.DbAccess.ExecuteNonQuery(
                        cmd =>
                            {
                                cmd.CommandText = string.Format("{{call {0}(?)}}",
                                    _domain.GetObjectFullName(Db.Procedures.GetDbmsName));

                                cmd.Parameters.Add(productName);
                            });

                    _productName = (string) productName.Value;
                }

                return _productName;
            }
        }

        /// <summary>
        ///   Gets the database management system version.
        /// </summary>
        /// <value>The string representation of the database management system version.</value>
        public string ProductVersion
        {
            get
            {
                if (_productVersion == null)
                {
                    var productVersion = _domain.DbAccess.CreateParameter("result", OdbcType.NVarChar, string.Empty);
                    productVersion.Size = 64;
                    productVersion.Direction = ParameterDirection.Output;

                    _domain.DbAccess.ExecuteNonQuery(
                        cmd =>
                            {
                                cmd.CommandText = string.Format("{{call {0}(?)}}",
                                    _domain.GetObjectFullName(Db.Procedures.GetDbmsVersion));

                                cmd.Parameters.Add(productVersion);
                            });

                    _productVersion = (string) productVersion.Value;
                }

                return _productVersion;
            }
        }


        /// <summary>
        ///   Gets the major version of the domain metadata.
        /// </summary>
        public int MetadataMajorVersion
        {
            get
            {
                var resultValue = 0;

                _domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1}",
                            Db.Tables.MetadataVersion.Col.Major,
                            _domain.GetObjectFullName(Db.Tables.MetadataVersion.Name));
                    },
                    result =>
                    {
                        resultValue = DbValue.GetInteger(result[Db.Tables.MetadataVersion.Col.Major])
                            .GetValueOrDefault();
                    });

                return resultValue;
            }
        }


        /// <summary>
        ///   Gets the minor version of the domain metadata.
        /// </summary>
        public int MetadataMinorVersion
        {
            get
            {
                var resultValue = 0;

                _domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1}",
                            Db.Tables.MetadataVersion.Col.Minor,
                            _domain.GetObjectFullName(Db.Tables.MetadataVersion.Name));
                    },
                    result =>
                    {
                        resultValue = DbValue.GetInteger(result[Db.Tables.MetadataVersion.Col.Minor])
                            .GetValueOrDefault();
                    });

                return resultValue;
            }
        }


        /// <summary>
        ///   Gets the column renaming feature availability.
        /// </summary>
        /// <value>True if the current database management system supports column renaming; otherwise, false.</value>
        public bool HasColumnRenaming
        {
            get { return _columnRenaming ?? (_columnRenaming = ReadFeatureAvailability(
                Db.Tables.DbmsFeatureList.FeatureNames.ColumnRenaming)).Value; }
            private set
            {
                SetFeatureAvailability(Db.Tables.DbmsFeatureList.FeatureNames.ColumnRenaming, value);

                _columnRenaming = value;
            }
        }

        /// <summary>
        ///   Checks the database version and vendor and adjusts available features if necessary.
        /// </summary>
        internal void AdjustDbmsFeatures()
        {
            var productName = ProductName.ToLower();

            // Parsing product version
            var productMajorVersion = 0;
            var productMinorVersion = 0;

            var productVersionStr = ProductVersion;

            var versionParts = productVersionStr.Split('.');

            if (versionParts.Length > 0)
                productMajorVersion = int.Parse(versionParts[0]);

            if (versionParts.Length > 1)
                productMinorVersion = int.Parse(versionParts[1]);

            switch (productName)
            {
                case "oracle":
                    if ((productMajorVersion < 9) || (productMajorVersion == 9 && productMinorVersion < 2))
                        HasColumnRenaming = false;
                    else
                        HasColumnRenaming = true;
                    break;
                case "sqlserver":
                    HasColumnRenaming = true;
                    break;
            }
        }

        /// <summary>
        ///   Retrieves the specified feature avalilability.
        /// </summary>
        /// <param name = "featureName">The specified feature name.</param>
        /// <returns>Returns true if the specified feature is available; otherwise returns false.</returns>
        private bool ReadFeatureAvailability(string featureName)
        {
            var resultValue = true;

            _domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        Db.Tables.DbmsFeatureList.Col.IsEnabled,
                        _domain.GetObjectFullName(Db.Tables.DbmsFeatureList.Name),
                        Db.Tables.DbmsFeatureList.Col.FeatureName);

                    cmd.Parameters.Add(_domain.DbAccess.CreateParameter("feature_name", OdbcType.NVarChar, featureName));
                },
                result => resultValue = Convert.ToInt16(result) == 1);

            return resultValue;
        }

        /// <summary>
        ///   Sets the specified feature availability.
        /// </summary>
        /// <param name = "featureName">The specified feature name.</param>
        /// <param name = "isEnabled">True if the specified feature is available; otherwise false.</param>
        private void SetFeatureAvailability(string featureName, bool isEnabled)
        {
            _domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=? where {2}=?",
                        _domain.GetObjectFullName(Db.Tables.DbmsFeatureList.Name),
                        Db.Tables.DbmsFeatureList.Col.IsEnabled,
                        Db.Tables.DbmsFeatureList.Col.FeatureName);

                    cmd.Parameters.Add(_domain.DbAccess.CreateParameter("is_enabled", OdbcType.SmallInt, isEnabled ? 1 : 0));
                    cmd.Parameters.Add(_domain.DbAccess.CreateParameter("feature_name", OdbcType.NVarChar, featureName));
                });
        }

        /// <summary>
        ///   Refreshes the metadata properties.
        /// </summary>
        public void Refresh()
        {
            _productName = null;
            _productVersion = null;
            _columnRenaming = null;
        }
    }
}